#include "waylandWorker.h"

#include <csignal>
#include <cstdio>
#include <cstring> // Include the header file for 'strdup'
#include <unistd.h>

#ifndef ARRAY_LENGTH
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0])
#endif

#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif

volatile bool WaylandWorker::should_quit_ = false;

const struct wl_registry_listener WaylandWorker::registry_listener_ = {WaylandWorker::registry_handle_global,
                                                                       WaylandWorker::registry_handle_global_remove};

const struct wl_output_listener WaylandWorker::output_listener_ = {WaylandWorker::output_handle_geometry,
                                                                   WaylandWorker::output_handle_mode,
                                                                   WaylandWorker::output_handle_done,
                                                                   WaylandWorker::output_handle_scale};

const struct wl_seat_listener WaylandWorker::seat_listener_ = {
    seat_handle_capabilities,
    seat_handle_name,
};

const struct wl_shell_surface_listener WaylandWorker::shell_surface_listener_ = {WaylandWorker::shell_surface_handle_ping,
                                                                                 WaylandWorker::shell_surface_handle_configure,
                                                                                 WaylandWorker::shell_surface_handle_popup_done};

WaylandWorker::WaylandWorker()
    : display_(0)
    , window_(0)
{
}

WaylandWorker::~WaylandWorker()
{
}

bool WaylandWorker::init_display()
{
    struct sigaction sa;
    sa.sa_handler = &WaylandWorker::quit_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    display_ = new struct my_display();
    if (!display_) {
        return false;
    }

    display_->display = wl_display_connect(NULL);
    if (!display_->display) {
        return false;
    }

    display_->registry = wl_display_get_registry(display_->display);
    wl_registry_add_listener(display_->registry, &registry_listener_, this);
    wl_display_roundtrip(display_->display);

    return true;
}

void WaylandWorker::cleanup_display()
{
    if (window_) {
        wl_shell_surface_destroy(window_->shell_surface);
        wl_egl_window_destroy(window_->native);
        wl_surface_destroy(window_->surface);
        delete window_;
    }

    if (display_->seatInfo) {
        if (display_->seatInfo->keyboard) {
            wl_keyboard_destroy(display_->seatInfo->keyboard);
        }
        delete display_->seatInfo;
    }

    if (display_->compositor) {
        wl_compositor_destroy(display_->compositor);
    }

    if (display_->shell) {
        wl_shell_destroy(display_->shell);
    }

    if (display_->registry) {
        wl_registry_destroy(display_->registry);
    }

    if (display_->display) {
        wl_display_flush(display_->display);
        wl_display_disconnect(display_->display);
    }

    delete display_;
}

void WaylandWorker::registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version)
{
    WaylandWorker *that = static_cast<WaylandWorker *>(data);
    std::string inter = std::string(interface);
    that->m_interfaces.push_back(inter);

    printf("[waylandDemo] interface: %-50s version: %2u, id: %2u\n", interface, version, id);

    if (!strcmp(interface, "wl_seat")) {
        seat_info *seatInfo = new seat_info();
        that->display_->seatInfo = seatInfo;

        wl_seat *seat = static_cast<struct wl_seat *>(wl_registry_bind(registry, id, &wl_seat_interface, MIN(version, 4)));
        seatInfo->seat = seat;

        wl_seat_add_listener(seat, &seat_listener_, data);
        wl_display_roundtrip(that->display_->display);

    } else if (!strcmp(interface, "wl_shm")) {
    } else if (strcmp(interface, "wl_compositor") == 0) {
        that->display_->compositor = static_cast<struct wl_compositor *>(wl_registry_bind(registry, id, &wl_compositor_interface, 1));
    } else if (strcmp(interface, "wl_shell") == 0) {
        that->display_->shell = static_cast<struct wl_shell *>(wl_registry_bind(registry, id, &wl_shell_interface, 1));
    } else if (strcmp(interface, "wl_output") == 0) {
        struct my_output *my_output = new struct my_output();
        memset(my_output, 0, sizeof(*my_output));
        my_output->output = static_cast<struct wl_output *>(wl_registry_bind(registry, id, &wl_output_interface, 2));
        that->display_->outputs.push_back(my_output);

        wl_output_add_listener(my_output->output, &output_listener_, my_output);
        wl_display_roundtrip(that->display_->display);
    }
}

void WaylandWorker::registry_handle_global_remove(void * /*data*/, struct wl_registry * /*registry*/, uint32_t /*name*/)
{
}

void WaylandWorker::seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps)
{
    // enum wl_seat_capability real_caps = (enum wl_seat_capability)caps;
    WaylandWorker *that = static_cast<WaylandWorker *>(data);
    struct seat_info *seatInfo = that->display_->seatInfo;
    seatInfo->capabilities = caps;
    printf("\tcapabilities:");

    if (caps & WL_SEAT_CAPABILITY_POINTER)
        printf(" pointer");
    if (caps & WL_SEAT_CAPABILITY_KEYBOARD)
        printf(" keyboard");
    if (caps & WL_SEAT_CAPABILITY_TOUCH)
        printf(" touch");

    printf("\n");
    if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
        seatInfo->keyboard = wl_seat_get_keyboard(wl_seat);
        // wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat);
        // wl_display_roundtrip(that->display_->display);
    }
}

void WaylandWorker::seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name)
{
    WaylandWorker *that = static_cast<WaylandWorker *>(data);
    struct seat_info *seatInfo = that->display_->seatInfo;
    seatInfo->name = strdup(name);
    printf("seat name: %s\n", seatInfo->name);
}

void WaylandWorker::output_handle_geometry(void * /*data*/,
                                           struct wl_output * /*wl_output*/,
                                           int32_t /*x*/,
                                           int32_t /*y*/,
                                           int32_t /*physical_width*/,
                                           int32_t /*physical_height*/,
                                           int32_t /*subpixel*/,
                                           const char * /*make*/,
                                           const char * /*model*/,
                                           int32_t /*transform*/)
{
}

void WaylandWorker::output_handle_mode(void *data, struct wl_output * /*wl_output*/, uint32_t flags, int32_t width, int32_t height, int32_t refresh)
{
    /* Only handle output mode events for the shell's "current" mode */
    if (flags & WL_OUTPUT_MODE_CURRENT) {
        struct my_output *my_output = static_cast<struct my_output *>(data);

        my_output->width = width;
        my_output->height = height;
        my_output->refresh = refresh;
    }
}

void WaylandWorker::output_handle_done(void * /*data*/, struct wl_output * /*wl_output*/)
{
}

void WaylandWorker::output_handle_scale(void * /*data*/, struct wl_output * /*wl_output*/, int32_t /*factor*/)
{
}

void WaylandWorker::shell_surface_handle_ping(void * /*data*/, struct wl_shell_surface *shell_surface, uint32_t serial)
{
    wl_shell_surface_pong(shell_surface, serial);
}

void WaylandWorker::shell_surface_handle_popup_done(void * /*data*/, struct wl_shell_surface * /*shell_surface*/)
{
}

void WaylandWorker::shell_surface_handle_configure(void *data,
                                                   struct wl_shell_surface * /*shell_surface*/,
                                                   uint32_t /*edges*/,
                                                   int32_t width,
                                                   int32_t height)
{
    WaylandWorker *that = static_cast<WaylandWorker *>(data);
    that->window_->properties.width = width;
    that->window_->properties.height = height;
    wl_egl_window_resize(that->window_->native, width, height, 0, 0);
}

bool WaylandWorker::create_window(WindowProperties const &properties)
{
    struct my_output *output = 0;
    if (!display_->outputs.empty())
        output = display_->outputs.at(0);
    window_ = new struct my_window();
    window_->properties = properties;
    window_->surface = wl_compositor_create_surface(display_->compositor);
    if (window_->properties.fullscreen && output) {
        window_->native = wl_egl_window_create(window_->surface, output->width, output->height);
        window_->properties.width = output->width;
        window_->properties.height = output->height;
    } else {
        window_->native = wl_egl_window_create(window_->surface, properties.width, properties.height);
    }

    struct wl_region *opaque_reqion = wl_compositor_create_region(display_->compositor);
    wl_region_add(opaque_reqion, 0, 0, window_->properties.width, window_->properties.height);
    wl_surface_set_opaque_region(window_->surface, opaque_reqion);
    wl_region_destroy(opaque_reqion);

    window_->shell_surface = wl_shell_get_shell_surface(display_->shell, window_->surface);
    if (window_->shell_surface) {
        wl_shell_surface_add_listener(window_->shell_surface, &shell_surface_listener_, this);
    }

    wl_shell_surface_set_title(window_->shell_surface, "wayland-demo");

    if (window_->properties.fullscreen) {
        wl_shell_surface_set_fullscreen(window_->shell_surface, WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER, output->refresh, output->output);
    } else {
        wl_shell_surface_set_toplevel(window_->shell_surface);
    }

    return true;
}

void WaylandWorker::quit_handler(int /*signum*/)
{
    should_quit_ = true;
    exit(0);
}

bool WaylandWorker::should_quit()
{
    return should_quit_;
}
